package com.gitee.cui.zookeeper.nameservice;

/**
 * 模拟雪花算法id生成器，真正做项目时建议采用开源的id生成器
 *
 * @author cuiqingqiang
 * @version 1.0
 * @date 2021-11-06 13:46
 **/
public class SnowflakeIdGenerator {

	private SnowflakeIdGenerator() {}

	public static final SnowflakeIdGenerator INSTANCE = new SnowflakeIdGenerator();

	/**
	 * 开始使用该算法的时间为: 2017-01-01 00:00:00
	 */
	private static final long START_TIME = 1483200000000L;

	/**
	 * worker id的位数，最多支持8192个字节
	 */
	private static final int WORK_ID_BITS = 13;

	/**
	 * 序列号位数，支持每毫秒单节点最多1024个
	 */
	private static final int SEQUENCE_ID_BITS = 10;

	/**
	 * 最大的 worker id ，8091
	 * -1 的补码（二进制全1）右移13位, 然后取反
	 */
	private static final long MAX_WORKER_ID = ~(-1L << WORK_ID_BITS);

	/**
	 * 最大的序列号，1023
	 * -1 的补码（二进制全1）右移10位, 然后取反
	 */
	private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_ID_BITS);

	/**
	 * worker 节点编号的移位
	 */
	private static final int APP_HOST_ID_SHIFT = SEQUENCE_ID_BITS;

	/**
	 * 时间戳的移位
	 */
	private static final int TIMESTAMP_LEFT_SHIFT = APP_HOST_ID_SHIFT + WORK_ID_BITS;

	/**
	 * 该项目的worker 节点 id
	 */
	private long workerId;

	/**
	 * 上次生成ID的时间戳
	 */
	private long lastTimestamp = -1L;

	/**
	 * 当前毫秒生成的序列
	 */
	private long sequence = 0L;

	/**
	 * 初始化worker id
	 * @param workerId
	 */
	public synchronized void init(long workerId) {
		if (workerId > MAX_WORKER_ID) {
			throw new IllegalArgumentException("worker id wrong: " + workerId);
		}
		INSTANCE.workerId = workerId;
	}

	/**
	 * 获取下一个id
	 * @return id
	 */
	public long nextId() {
		return generateId();
	}

	/**
	 * 生成下一个id
	 * @return 下一个id
	 */
	private synchronized long generateId() {
		// 当前时间戳
		long current = System.currentTimeMillis();
		if (current < lastTimestamp) {
			// 如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过，出现问题返回-1
			return -1;
		}

		if (current == lastTimestamp) {
			// 如果当前生成id的时间还是上次的时间，那么对sequence序列号进行+1
			sequence = (sequence + 1) & MAX_SEQUENCE;
			if (sequence == MAX_SEQUENCE) {
				// 如果已经是当前时间戳的最大id，则等待下一个时间戳
				current = nextMs(lastTimestamp);
			}
		} else {
			// 当前时间戳已经是下一个时间戳，可以从0开始生成当前时间戳的序号
			sequence = 0L;
		}

		// 更新上次生成id的时间戳
		lastTimestamp = current;

		// 时间戳左移23位
		long time = (current - START_TIME) << TIMESTAMP_LEFT_SHIFT;

		// worker id 左移10位
		long workerId = this.workerId << APP_HOST_ID_SHIFT;

		return time | workerId | sequence;
	}

	/**
	 * 获取下一个时间戳
	 * @param lastTimestamp 最后一个时间戳
	 * @return 下一个时间戳
	 */
	private long nextMs(long lastTimestamp) {
		long current = System.currentTimeMillis();
		while (current <= lastTimestamp) {
			current = System.currentTimeMillis();
		}
		return current;
	}
}
